package de.pass.ch.graph.edge;

import static java.lang.Integer.*;

import java.io.Serializable;

import de.pass.ch.util.Constants;

import static de.pass.ch.util.Constants.*;

/**
 * 
 * @author schmax1424
 * 
 */
public class Edge implements Serializable, Comparable<Edge> {

	private static final long serialVersionUID = -3631739682161578547L;

	private int target;
	private int weight;
	private int shortcutMiddle;
	private int shortcutOriginalEdgeCount;

	/** Constructs the reverse edge of the given edge. */
	public Edge(int target, Edge reverseEdge) {
		this.target = target;
		this.weight = reverseEdge.getWeight();
		makeOpenReversed(reverseEdge);
		setType(reverseEdge.getType());
		copyShortcutInfo(reverseEdge, true);
	}

	/**
	 * Konstruktor fuer die Deserialisierung des Suchgraphen.
	 * 
	 * @param target
	 * @param weight
	 * @param shortcutMiddle
	 * @param shortcutOriginalEdgeCount
	 */
	public Edge(int target, int weight, int shortcutMiddle,
			int shortcutOriginalEdgeCount) {
		this.target = target;
		this.weight = weight;
		this.shortcutMiddle = shortcutMiddle;
		this.shortcutOriginalEdgeCount = shortcutOriginalEdgeCount;
	}

	/**
	 * Constructor for an edge
	 * 
	 * @param target
	 *            - node, that represents the target of this edge
	 * @param weight
	 *            - weight of this edge
	 * @param type
	 *            - Type of the edge (see {@link Constants})
	 * @param forward
	 *            - true, iff this edge is directed forward
	 * @param backward
	 *            - true, iff this edge is directed backward
	 */
	public Edge(int target, int weight, byte type, boolean forward,
			boolean backward) {

		this(target, weight, type, forward, backward, SPECIAL_NODEID,
				SPECIAL_NODEID, SPECIAL_NODEID, 1);

	}

	/**
	 * Constructor for an edge
	 * 
	 * @param target
	 *            - node, that represents the target of this edge
	 * @param weight
	 *            - weight of this edge
	 * @param type
	 *            - Type of the edge (see {@link Constants})
	 * @param forward
	 *            - true, iff this edge is directed forward
	 * @param backward
	 *            - true, iff this edge is directed backward
	 * @param shortcutEdge1
	 *            - relative index into the edge array of the first edge of the
	 *            path
	 * @param shortcutEdge2
	 *            - relative index into the edge array of the second edge of the
	 *            path
	 * @param shortcutMiddle
	 *            - middle node of the path a shortcut represents
	 * @param shortcutOriginalEdgeCount
	 *            - shows the number of original edges represented by this edge
	 */
	public Edge(int target, int weight, byte type, boolean forward,
			boolean backward, int shortcutEdge1, int shortcutEdge2,
			int shortcutMiddle, int shortcutOriginalEdgeCount) {

		this.target = target;
		this.weight = weight;
		this.shortcutMiddle = shortcutMiddle;
		this.shortcutOriginalEdgeCount = shortcutOriginalEdgeCount;

		setShortcutEdge1(Math.min(shortcutEdge1, SHORTCUT_EDGE_LIMIT));
		setShortcutEdge2(Math.min(shortcutEdge2, SHORTCUT_EDGE_LIMIT));
		setForward(forward);
		setBackward(backward);

		if (type < 0 || type > 2)
			type = 0;

		setType(type);

	}

	public int getTarget() {
		return target & (MAX_VALUE >> 5);
	}

	public int getWeight() {
		return weight & (MAX_VALUE >> 3);
	}

	public int getShortcutMiddle() {
		return shortcutMiddle & (MAX_VALUE >> 5);
	}

	public int getShortcutOriginalEdgeCount() {
		return shortcutOriginalEdgeCount;
	}

	public int getShortcutEdge1() {
		return (target & (SHORTCUT_EDGE_LIMIT << 26)) >>> 26;
	}

	public int getShortcutEdge2() {
		return (shortcutMiddle & (SHORTCUT_EDGE_LIMIT << 26)) >>> 26;
	}

	public byte getType() {
		return (byte) ((weight & (0x3 << 30)) >>> 30);
	}

	public boolean isForward() {
		return (weight & (0x1 << 29)) != 0;
	}

	public boolean isBackward() {
		return (weight & (0x1 << 28)) != 0;
	}

	public boolean isBidirected() {
		return isForward() && isBackward();
	}

	public boolean isClosed() {
		return !isForward() && !isBackward();
	}

	public void setTarget(int t) {
		target = (target | t) & (t | (SHORTCUT_EDGE_LIMIT << 26));
	}

	public void setWeight(int w) {
		weight = (weight | w) & (w | (SHORTCUT_EDGE_LIMIT << 28));
	}

	public void setShortcutMiddle(int s) {
		shortcutMiddle = (shortcutMiddle | s)
				& (s | (SHORTCUT_EDGE_LIMIT << 26));
	}

	public void setShortcutOriginalEdgeCount(int shortcutOriginalEdgeCount) {
		this.shortcutOriginalEdgeCount = shortcutOriginalEdgeCount;
	}

	public void setShortcutEdge1(int se1) {
		se1 = se1 << 26;
		target = (target | se1) & (se1 | (MAX_VALUE >> 5));
	}

	public void setShortcutEdge2(int se2) {
		se2 = se2 << 26;
		shortcutMiddle = (shortcutMiddle | se2) & (se2 | (MAX_VALUE >> 5));
	}

	public void setType(byte type) {
		switch (type) {
		case EDGE_TYPE_ORIGINAL:
			weight = weight & ~(0x3 << 30);
			break;
		case EDGE_TYPE_SHORTCUT:
			weight = (weight | (0x1 << 30)) & ~(0x1 << 31);
			break;
		case EDGE_TYPE_WITNESS_SHORTCUT:
			weight = (weight | (0x1 << 31)) & ~(0x1 << 30);
			break;
		}
	}

	public void setForward(boolean forward) {
		if (forward) {
			weight = weight | (0x1 << 29);
		} else {
			weight = weight & ~(0x1 << 29);
		}
	}

	public void setBackward(boolean backward) {
		if (backward) {
			weight = weight | (0x1 << 28);
		} else {
			weight = weight & ~(0x1 << 28);
		}
	}

	public void makeClosed() {
		setForward(false);
		setBackward(false);
	}

	public boolean isDirected(byte dir) {
		assert (dir == 0 || dir == 1) : "dir is not in [0,1]!";

		if (dir == 0) {
			return isForward();
		}
		return isBackward();
	}

	public boolean isReverse(int nodeID, Edge edge) {
		return ((getTarget() == nodeID) && (getWeight() == edge.getWeight())
				&& (isDirected((byte) 0) == edge.isDirected((byte) 1))
				&& (isDirected((byte) 1) == edge.isDirected((byte) 0)) && (getType() == edge
					.getType()));
	}

	public void copyShortcutInfo(Edge edge, boolean reverse) {
		shortcutOriginalEdgeCount = edge.getShortcutOriginalEdgeCount();
	}

	public void makeOneWay(byte dir) {
		assert (dir == 0 || dir == 1) : "dir out of bounds!";
		setForward(dir == 0);
		setBackward(dir == 1);
	}

	public void makeTwoWay() {
		setForward(true);
		setBackward(true);
	}

	public int getRealTarget() {
		return target;
	}

	public int getRealWeight() {
		return weight;
	}

	public int getRealshortcutMiddle() {
		return shortcutMiddle;
	}

	public int getRealshortcutOriginalEdgeCount() {
		return shortcutOriginalEdgeCount;
	}

	/**
	 * Ensures that this edge is at least open in the opposite directions as the
	 * given edge.
	 */
	public void makeOpenReversed(Edge e) {
		setBackward(e.isForward());
		setForward(e.isBackward());
	}

	/**
	 * Compares this Edge with the given Edge e first by target, then by weight.
	 * 
	 * @param e
	 *            - Edge, to compare with
	 * @return -1, if target or weight of this Edge are smaller than target or
	 *         weight of Edge e; 1, if target or weight of this Edge are larger
	 *         than target or weight of Edge e; else 0
	 */
	@Override
	public int compareTo(Edge e) {
		if (getTarget() == e.getTarget()) {
			if (getWeight() == e.getWeight()) {
				return 0;
			}
			return getWeight() < e.getWeight() ? -1 : 1;
		}
		return getTarget() < e.getTarget() ? -1 : 1;
	}

	public boolean equals(Edge e) {
		if (getTarget() == e.getTarget()) {
			if (isForward() == e.isForward() && isBackward() == e.isBackward()) {
				return true;
			}
		}
		return false;
	}

	@Override
	public String toString() {
		String res = "";
		res += getTarget() + " : " + getWeight() + " ";
		res += isBackward() ? "<" : " ";
		res += "-";
		res += isForward() ? ">" : " ";
		return res;
	}

}
